﻿using System;
using System.Drawing;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Remoting.Messaging;
using System.Windows.Forms;
using System.Threading;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.IO;

namespace Server
{
    public partial class Server : Form
    {

        #region Costanti
        /// <summary>
        /// Definisco il numero massimo di clienti che il server riesce a gestire.
        /// </summary>
        public const int MaxNumClienti = 4; 
        #endregion

        #region Variabili

        private int porta = 20000; // un default, ma comunque obbligo l'utente a inserire il numero di porta
        private Cliente[] clienti; // Vettore contente le informazioni dei vari client
        private Socket socket; // socket 
        private int numClientiConnessi; // numero clienti connessi
        private string password; // password valida per la sessione
        private Image immagine; //contiene l'immagine che verrà visualizzate e inviata
        private IntPtr HwndDaInviare; // contiene il riferimento alla Hwnd da copiare e inviare
        private IDataObject oggetto_clipboard; // contiene l'oggetto che verrà condiviso con i client
        private Rectangle porzione_schermo; // contiene il riferimento alla porzione di schermo che condivido
        private Size panel1Size; // contiene le informazioni sulla grandezza del form
        private bool doing_work = false; // Variabile usata in polling per capire quando il thread di analisi e invio dello schermo ha finito
        private bool inizio_selezione_finestra;  // serve per far partire il secondo hook nella selezione della finestra
        private TransparentForm transparentform; // Form che mi circonda la porzione di schermo da inviare
        private bool choose_rect = false; // Indica se è in corso la scelta della porzione di schermo
        
        #endregion

        #region Semafori

        private Object lock_clienti = new Object(); // lock da usare quando si vuole accedere alla variabile clienti
        private Object lock_BoxImmagine = new Object(); // lock da usare quando si aggiorna l'immagine 
        #endregion

        #region Costruttore
        
        /// <summary>
        /// Costruttore della classe Server  inizializza la finestra pricipale dopo aver chiamato il controllo per nome utente e password
        /// </summary>
        public Server()
        {
            
            InitializeComponent(); // crea L'interdaccia grafica

            
       
                Console.WriteLine("Il thread pricnipale è: " + System.Threading.Thread.CurrentThread.ManagedThreadId); // debug
                transparentform = new TransparentForm();
                Porta_password_control dialog = new Porta_password_control(); // creo una nuova istanza della finestra Porta_password_controll
                if (dialog.ShowDialog(this) == DialogResult.OK) // aspetto che restituisca DialogResult OK
                {
                    this.porta = dialog.Porta; // la porta
                    this.password = dialog.Password; // la password
                    dialog.Dispose(); // distruggo la finestra di dialogo
                    clienti = new Cliente[MaxNumClienti]; // alloco il vettore per gestire i clienti
                    numClientiConnessi = 0;
                    ChatBrowser.Navigate("About:blank"); //vado alla pagina di dieafult bianca
                    ChatBrowser.Document.Write(Properties.Resources.page_style); // modifico la pagina di default con quella del page style
                    CreaSocket();
                    this.HwndDaInviare = USER32.GetDesktopWindow();  // Il default è che mando tutto il contenuto dello schermo
                    BoxImmagine.SizeMode = PictureBoxSizeMode.Zoom;
                    this.porzione_schermo = Rectangle.Empty;
                    this.panel1Size = this.Size;
                    RettangoloRossoTefresh.Start();
                    //InvalidaSchermo.Start();
                }
                else
                {
                    this.Close();
                }
            
        }

        #endregion

        #region Network

        #region Invio Clipboard
        /// <summary>
        /// Si occupa di inviare il contenuto della clipboard al cliente 
        /// </summary>
        /// <param name="nome_cliente">Il nome del cliente a cui mandare la clipboard</param>
        private void InvioCLipboard(object nome_cliente)
        {
            int i;
            if (this.NomePresente((string)nome_cliente))
                i = this.TrovaNome((string)nome_cliente);
            else
            {
                return;
            }
            try
            {
                Messaggio m = new Messaggio(oggetto_clipboard);
                clienti[i].invia(m);
            }
            catch (FileTooBigException)
            {
                MessageBox.Show("Non puoi Condividere File più grandi di 25 MB");
            }
        }
        #endregion

        /// <summary>
        /// Funzione che crea un nuovo socket ne associa un indirizzo e una coda
        /// inoltre chiama la BeginAccept e gli associa una funzione di callback che viene richiamata
        /// quando un nuovo client si connette
        /// </summary>
        private void CreaSocket()
        {
            try
            {
                IPEndPoint lep = new IPEndPoint(IPAddress.Any, this.porta); // posso ricevere da qualsiasi scheda di rete ma tutte alla porta specificata

                socket = new Socket(lep.AddressFamily,
                                        SocketType.Stream, // tipo di connessione TCP
                                        ProtocolType.Tcp);

                socket.Bind(lep); // associo al socket il suo indirizzo
                socket.Listen(1000); // coda massima di richieste sul socket
                socket.BeginAccept(new AsyncCallback(AccettaCliente), socket);// Chiamata a accept asincrona (thread separato)
            }
            catch (Exception)
            {
                MessageBox.Show("La porta selezionata è occupata da un'altro processo");
                this.Dispose();
            }
        }

        /// <summary>
        /// Funzione che si preoccupa di accettare nuove connesioni TCP con il server,
        /// E' una funzione di callback che riceve come parametro un IAsyncResult che casto a socket
        /// </summary>
        /// <param name="result"> Socket del server</param>
        private void AccettaCliente(IAsyncResult result)
        {
            //debug
            Console.WriteLine("Accept gestita da Thread: " + System.Threading.Thread.CurrentThread.ManagedThreadId); // debug
            
            Socket originale = (Socket)result.AsyncState; // il socket su cui ho fatto la begin Accept
            Socket cliente = originale.EndAccept(result); // La endResult mi restituisce  una copia del socket connesso
            // Ho accettato la connessione di un nuovo cliente, ora controllo se ho posto per lui se si lo aggiungo ai clienti altrimenti chiudo il socket.
            lock (this.lock_clienti) // voglio che sia una regione critica poiché accedo a risorse condivise
            {
                int x;
                if (( x= TrovaClient((IPEndPoint)cliente.RemoteEndPoint)) < Server.MaxNumClienti && clienti[x].autenticato)
                {
                    if(!clienti[x].FrameSocketSet)
                    {
                        while (this.doing_work) ;
                        lock (this.lock_BoxImmagine)
                        {
                            doing_work = true;
                            EliminaImmagine();
                            clienti[x].FrameSocket = cliente; // mi assicuro che il prossimo frame che invio è quello globale
                            doing_work = false;
                        }
                    }
                    else
                        clienti[x].ClipboardSocket = cliente;
                }
                else if (numClientiConnessi < Server.MaxNumClienti)
                {
                    for (int i = 0; i < Server.MaxNumClienti; i++) // cerco la posizione del vettore libera
                        if (clienti[i] == null)
                        {
                            clienti[i] = new Cliente("", cliente); // la assegno
                            clienti[i].ricevi(AutenticaCliente); // il primo messaggio che devo ricevere è quello di autenticazionedi conseguneza chiamo una callback per quel cliente
                            numClientiConnessi++;
                            break;
                        }
                }
                else
                {
                    cliente.Shutdown(SocketShutdown.Both); // chiudo il socket del cliente poiché non ho spazio per gestirlo
                    cliente.Close();
                }
                socket.BeginAccept(new AsyncCallback(AccettaCliente), socket); // sono pronto ad accettare nuovi clienti
            }
        }


        /// <summary>
        /// Callback che gestisce i messaggi in arrivo
        /// visualizza questo messaggio e rispedirlo agli altri client
        /// </summary>
        /// <param name="result"> </param>
        private void RiceviMsgCliente(IAsyncResult result)
        {

            try
            {
                // devo castare IAsyncResult per poter richiamare il delegato e finire la procedura di callback
                AsyncResult r = (AsyncResult)result;
                Cliente.RiceviMsgDelegate d = (Cliente.RiceviMsgDelegate)r.AsyncDelegate;
                Messaggio m = (Messaggio)d.EndInvoke(r);
                if (m == null)
                {
                    Socket s = (Socket)r.AsyncState;
                    int i = TrovaClient(s);
                    AggiornaChat("Admin", clienti[i].NomeCliente + " Disconesso");
                    EliminaCliente(i);
                }
                else
                {
                    if (m.DownloadClipboard) // devo inviargli il contenuto della clipboard memorizzato
                    {
                        // chiamo un delegato a cui passo nome del cliente a cui mandare il contenuto della clipboard
                        ThreadPool.QueueUserWorkItem(new WaitCallback(InvioCLipboard), m.Nome); // creo un thread che gestisce l'invio della clipboard al client
                        lock (lock_clienti)
                        {
                            clienti[TrovaNome(m.Nome)].ricevi(RiceviMsgCliente);
                        }
                    }
                    else // è un messaggio di testo
                    {
                        AggiornaChat(m.Nome, m.Text);
                        ReinviaMessaggio(m);
                    }

                }
            }
            catch (SocketException) // errori vari ed eventuali
            {
                // ancora da implementare;
            }
            catch (System.IO.IOException) // errore sullo stream --> devo chiudere il socket
            {
                AsyncResult r = (AsyncResult)result;
                Socket s = (Socket)r.AsyncState;
                int i = TrovaClient(s);
                AggiornaChat("Admin", clienti[i].NomeCliente + " Disconesso");
                EliminaCliente(i);
            }
            catch (System.Runtime.Serialization.SerializationException)
            {
                AsyncResult r = (AsyncResult)result;
                Socket s = (Socket)r.AsyncState;
                int i = TrovaClient(s);
                AggiornaChat("Admin", clienti[i].NomeCliente + " Disconesso");
                EliminaCliente(i);
            }
            catch (ErroreReceiveException)
            {
                AsyncResult r = (AsyncResult)result;
                Socket s = (Socket)r.AsyncState;
                int i = TrovaClient(s);
                AggiornaChat("Admin", clienti[i].NomeCliente + " Disconesso");
                EliminaCliente(i);
            }
            catch (NullReferenceException)
            {
                // vuol dire che il client si è disconnesso e qualche altra funzione lo ha eliminato ---> non deve succedere perché io elimino i client solo qui
            }
        }

        /// <summary>
        /// Invia il messaggio a tutti i client con nome diverso rispetto a quello contenuto nel parametro
        /// </summary>
        /// <param name="m"> Messaggio da reinviare</param>
        private void ReinviaMessaggio(Messaggio m)
        {
            lock (lock_clienti)
            {
                foreach (Cliente c in this.clienti)
                {
                    if (c != null)
                    {
                        if (c.NomeCliente != m.Nome)
                            c.invia(m); // lo invio ad altri clienti
                        else
                            c.ricevi(RiceviMsgCliente); // Ho finito di gestire il messaggio del Cliente quindi sono pronto a riceverne un'altro 
                    }
                }
            }
        }

        /// <summary>
        /// Questa funzione si deve occupare di autenticare il cliente, se il cliente sbaglia la password 
        /// lui chiude il socket (sarà poi il cliente a ripetere l'operazione dall'inizio)
        /// </summary>
        /// <param name="result"> </param>
        private void AutenticaCliente(IAsyncResult result)
        {
            // devo castare IAsyncResult per poter richiamare il delegato e dinire la procedura di callback
            int i;
            // blocco il timer
            bool wasenable = TimerRefresh.Enabled;
            TimerRefresh.Stop();

            AsyncResult r = (AsyncResult)result;
            Cliente.RiceviMsgDelegate d = (Cliente.RiceviMsgDelegate)r.AsyncDelegate;
            Messaggio m = (Messaggio)d.EndInvoke(r);
            Socket sock = (Socket)r.AsyncState;

            if (m == null)
            {
                i = TrovaClient(sock);
                EliminaCliente(i);
            }
            // cerco in quale posizione ho il socket del cliente 
            try
            {
                lock (lock_clienti)
                {
                    i = TrovaClient(sock);

                    if (!m.PasswordSettata)
                    {
                        EliminaCliente(i);
                    }
                    else if (m.Password != this.password)
                    {
                        EliminaCliente(i);
                    }
                    else if (NomePresente(m.Nome))
                    {
                        EliminaCliente(i);
                    }
                    else
                    {
                        clienti[i].autenticato = true;
                        clienti[i].NomeCliente = m.Nome;
                        this.AggiornaChat("Admin", "Welcome " + clienti[i].NomeCliente);
                        InvioBroadCast(new Messaggio("Admin", "Welcome " + clienti[i].NomeCliente));
                        clienti[i].ricevi(RiceviMsgCliente); // sono pronto a ricevere messaggi della chat
                    }
                }
                if (wasenable)
                    this.StartStopCondivisione(null, null);
            }
            catch (ClienteNotFoundException)
            {
                // Baco gigantesco qualcuno ha cancellato il cliente sotto il mio naso
            }
        }

        /// <summary>
        /// Invio a tutti i client connessi il messaggio passatogli
        /// </summary>
        /// <param name="m"></param>
        private void InvioBroadCast(Messaggio m)
        {
            try
            {
                for (int i = 0; i < Server.MaxNumClienti; i++)
                {
                    if (clienti[i] != null && clienti[i].autenticato)
                        clienti[i].invia(m);
                }
            }
            catch (Exception)
            {
                // se qualcuno cancella il client prima che io abbia inviato
            }
        }
        #endregion

        #region Utility

        /// <summary>
        /// Mi restituisce la posizione del cliente nel vettore oppure genere una eccezione di cliente non trovato
        /// </summary>
        /// <param name="sock"> Socket del cliente da cercare, utile nelle callback nelle quali ho il socket</param>
        /// <returns></returns>
        private int TrovaClient(Socket sock)
        {
            int i;
            for (i = 0; i < Server.MaxNumClienti; i++)
            {
                if (clienti[i] != null) // un utente connesso 
                    if (clienti[i].Socket == sock)
                        break;
            }
            if(i >= Server.MaxNumClienti)
                throw new ClienteNotFoundException();
            else
                return i;
        }


        private int TrovaClient(IPEndPoint sock)
        {
            int i;
            for (i = 0; i < Server.MaxNumClienti; i++)
            {
                if (clienti[i] != null)
                {
                    IPEndPoint endpoint = (IPEndPoint)clienti[i].Socket.RemoteEndPoint;
                    if (endpoint.Address.ToString() ==  sock.Address.ToString())
                        return i;
                }
            }
                return i;
        }

        /// <summary>
        /// Provvede a eliminare i dati di un client che si è disconesso
        /// </summary>
        /// <param name="i"></param>
        private void EliminaCliente(int i)
       {    
               clienti[i].Socket.Close();
                if(clienti[i].FrameSocket != null)
                    clienti[i].FrameSocket.Close();
               if(clienti[i].ClipboardSocket != null)
                clienti[i].ClipboardSocket.Close();
               clienti[i] = null;
               numClientiConnessi--;
       }

        /// <summary>
        /// Controlla se c'è già un altro client con quel nome
        /// </summary>
        /// <param name="nome"></param>
        /// <returns></returns>
        private bool NomePresente(string nome)
       {
           for (int i = 0; i < Server.MaxNumClienti; i++)
           {
               if (clienti[i] != null)
                   if (clienti[i].NomeCliente == nome)
                       return true;
             
           }
           return false;
       }

        /// <summary>
        /// Trova il client con un dato nome
        /// </summary>
        /// <param name="nome"></param>
        /// <returns></returns>
        private int TrovaNome(string nome)
        {
            for (int i = 0; i < Server.MaxNumClienti; i++)
            {
                if (clienti[i] != null)
                    if (clienti[i].NomeCliente == nome)
                        return i;
            }
            return -1;
        }
        /// <summary>
        /// Elimina l'immagine di background, così si riparte da zero 
        /// </summary>
        private void EliminaImmagine()
        {
            lock (this.lock_BoxImmagine)
            {
                if (this.immagine != null)
                    this.immagine.Dispose();
                this.immagine = null;
            }
        }

        #endregion
    
        #region Aggiornamento Visualizzazione

        #region AggiornaChat
        /* sono una istanza della classe System.delegate con cui è
         *  possibile definire un metodo richiamabile da altri metodi.
         *  (simile a un puntatore a funzione)
        */
        delegate void AggiornaChatDelegate(string a, string b);   
        /// <summary>
        /// Metodo che si occupa di visualizzare il messaggio nella finestra webBrowser
        /// </summary>
        /// <param name="nome"></param>
        /// <param name="msg"></param>
        private void AggiornaChat(string nome,string msg)
        {
            // N.B Qui devo usare usare questo parematro per essere sicuro che sia il thread che ha creato il form a invocare il metodo Aggiorna chat se non è lui
            // e quindi InvokeRequired è true devo richiamare un delegato.
            if (this.InvokeRequired) // se è un thread diverso da quello che ha creato this allora è true
            {
                // eseguo il delegato creato nel thread proprietario della finestra in maniera asimcrona e thread safe 
               IAsyncResult r =  BeginInvoke(
            new AggiornaChatDelegate(AggiornaChat), 
            new object[] { nome, msg });
               EndInvoke(r);
            }
            else
            {
                if (nome == "Admin")
                {
                    string previous = this.ChatBrowser.Document.Body.InnerHtml;
                    string formattata =
                       "<p font-color:red> <b><i>" + nome + ": </b> </i>" +
                        msg + "</p>";
                    this.ChatBrowser.Document.Body.InnerHtml = previous + formattata;
                }
                else if (nome == "Io")
                {
                    string previous = this.ChatBrowser.Document.Body.InnerHtml;
                    string formattata =
                   "<div class=\"chat-bubble2\">" +
                       "<p> <b><i>" + nome + ": </b> </i>" +
                        msg + "</p>" +
                        //"<div class=\"chat-bubble-arrow-border\"></div>" +
                        //"<div class=\"chat-bubble-arrow\"></div>" +
                       "</div>" +
                   "</div>";
                    this.ChatBrowser.Document.Body.InnerHtml = previous + formattata;
                }
                else
                {
                    string previous = this.ChatBrowser.Document.Body.InnerHtml;
                    string formattata =
                   "<div class=\"chat-bubble\">" +
                       "<p> <b><i>" + nome + ": </b> </i>" +
                        msg + "</p>" +
                        //"<div class=\"chat-bubble-arrow-border\"></div>" +
                        //"<div class=\"chat-bubble-arrow\"></div>" +
                       "</div>" +
                   "</div>";
                    this.ChatBrowser.Document.Body.InnerHtml = previous + formattata;
                }
                    this.ChatBrowser.Document.Body.ScrollIntoView(false);
            }
        }
        #endregion

        #region Aggiorna Picture
        delegate void AggiornaPictureDelegate2(Bitmap m);
        private void AggiornaPicture(Bitmap image)
        {
            try
            {
                if (this.InvokeRequired)
                {
                    IAsyncResult r = BeginInvoke(new AggiornaPictureDelegate2(AggiornaPicture), new Bitmap(image));
                    EndInvoke(r);
                }
                else
                {
                    if (this.BoxImmagine.Image != null)
                        this.BoxImmagine.Image.Dispose(); // elimino  l'immagine precedente per evitare problemi di memoria                        
                    this.BoxImmagine.Image = new Bitmap(image); // Aggiorno la nuova immagine   
                    image.Dispose();
                }
            }
            catch (Exception) // Quando chiudo rileva una NullReference
            {
            }
        }
        #endregion
        
        #endregion

        #region Gestione Controlli Utente

        #region Bottone Send
        private delegate void ButttonInvioDelegate(string testo_msg);
        /// <summary>
        /// Metodo che si occupa di inviare a tutti i client il messaggio che l'utente sul server ha scritto nella chat 
        /// </summary>
        private void ButtonInvia_Click(object sender, EventArgs e)
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback(GestioneButtonClickInvio),this.ChatBox.Text);
            AggiornaChat("Io", this.ChatBox.Text);
            this.ChatBox.Text = "";
        }
        private void GestioneButtonClickInvio(Object s)
        {
                InvioBroadCast(new Messaggio("server", (string)s));
        }
        #endregion

        #region Scelta di Condivisione

        private void CondividiSchermoChoosed(object sender, EventArgs e)
        {
            if(this.ChooseHWnd.Enabled || this.choose_rect)
            {
                MessageBox.Show("Devi prima completare altra selezione");
            }
            else{
            this.condividiSchermoToolStripMenuItem.Checked = true;
            this.condividiFinestraToolStripMenuItem.Checked = false;
            this.porzioneSchermoToolStripMenuItem.Checked = false;
            this.porzione_schermo = Rectangle.Empty;

            if (TimerRefresh.Enabled)
            {
                StartStopCondivisione(null,null);
            }
            this.HwndDaInviare = USER32.GetDesktopWindow(); // nella HWND ho messo il riferimento al desktop
            }

        }

        private void CondividiFinestraChoosed(object sender, EventArgs e)
        {
            if (this.choose_rect)
            {
                MessageBox.Show("Devi prima completare la scleta del rettangolo");
            }
            else if (ChooseHWnd.Enabled)
            {
                MessageBox.Show("Devi prima completare la scleta della finestra");
            }
            else
            {
                if (TimerRefresh.Enabled)
                {
                    this.StartStopCondivisione(null, null);
                }
                MessageBox.Show("Per selezionare quale finestra condividere passarci sopra con il mouse, e premere con ilo tasto DESTRO del mouse");
                this.condividiSchermoToolStripMenuItem.Checked = false;
                this.condividiFinestraToolStripMenuItem.Checked = true;
                this.porzioneSchermoToolStripMenuItem.Checked = false;
                this.porzione_schermo = Rectangle.Empty;
                MouseHook.MouseClickedCallback mouseclicked = new MouseHook.MouseClickedCallback(FinestraChoosed);
                MouseHook hook = new MouseHook(mouseclicked);
                ChooseHWnd.Start();
            }
        }

        private void FinestraChoosed()
        {
            MouseHook.UnHook();
            ChooseHWnd.Stop();
        }
        #endregion

        #region Gestione Condivisione

        #region TimerRefresh
        /// <summary>
        /// Viene richiamata una volta ogni tot ms per aggiornare inviare in broadcast il frame e aggiornarlo
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void TimerRefresh_Tick(object sender, EventArgs e)
        {
            if (!doing_work)
            {
                this.doing_work = true;
                ThreadPool.QueueUserWorkItem(new WaitCallback(TimerRefreshGEstione));
            }
        }
        private void TimerRefreshGEstione(Object o)
        {
            Bitmap m2;
            Rectangle r;
            Messaggio m;
                m2 = CatturaSchermo.GetImageFromHwnd(this.HwndDaInviare);
                
               if (m2 == null)
                {
                    this.StartStopCondivisione(null, null);
                    MessageBox.Show("Hai chiuso la finestra in condivisione");
                    doing_work = false;
                    //this.CondividiSchermoChoosed(null, null);
                    return;
                }
                if (m2.Width == 1 && m2.Height == 1)
                {
                    if (TimerRefresh.Enabled)
                    {
                        this.StartStopCondivisione(null, null);
                    }
                    doing_work = false;
                    MessageBox.Show("Hai ridotto a icona la finestra di conseguenza termino la condivisione");
                   
                    return;
                }

                Point mouse_point = GetMouseinHwnd();
                lock (lock_BoxImmagine){
                    if (this.immagine == null) // è una porzione dello schermo
                    {
                        if (porzione_schermo != Rectangle.Empty)
                            this.immagine = ToolImmagini.CopyBitmap(m2, porzione_schermo);
                        else
                            this.immagine = new Bitmap(m2);
                        m = new Messaggio((Bitmap)ToolImmagini.Comprimi((Bitmap) this.immagine), mouse_point);
                    }
                    else
                    {
                        try
                        {
                            if (this.porzione_schermo != Rectangle.Empty) // è una porzione dello schermo
                            {
                                Bitmap copia = ToolImmagini.CopyBitmap(m2, porzione_schermo);
                                m2.Dispose();
                                m2 = new Bitmap(copia);
                                copia.Dispose();
                            }
                            r = ToolImmagini.DiffImage2((Bitmap)this.immagine, m2); // da verificare prestazioni
                            Bitmap img_partial = ToolImmagini.CopyBitmap(m2, r);
                            m = new Messaggio((Bitmap)ToolImmagini.Comprimi((Bitmap) img_partial), r, mouse_point);
                            Console.WriteLine("Rect is: " + r); // debug
                            this.immagine = ToolImmagini.CopyOnbackground((Bitmap)immagine, (Bitmap)img_partial, r);
                            img_partial.Dispose();
                        }
                        catch (DifferentSizeException)
                        {
                            m = new Messaggio((Bitmap)ToolImmagini.Comprimi((Bitmap) m2), mouse_point); 
                            this.immagine = new Bitmap(m2); 
                        }
                    }
                   AggiornaPicture((Bitmap)this.immagine); // aggiorno l'immagine sullo schermo del server
                }
            InvioBroadCast(m); // se c'è una eccezione fa lei il catch e la receive eliminerà il client
            m2.Dispose();
            this.doing_work = false;
        }
        #endregion

        /// <summary>
        /// Disegna un rettangolo rosso sullo schermo dove sto condividendo l'immagine
        /// </summary>
        private void RettangoloRosso()
        {
            if (this.porzione_schermo != Rectangle.Empty)
            {
                transparentform.Visible = true;
            }
            else
            {
                transparentform.Visible = false;
            }
        }
        /// <summary>
        /// Mi restituiece la posizione del mouse se è nella HWND che sto condividento alrimenti ritorna una posizione farlocca
        /// </summary>
        /// <returns></returns>
        private Point GetMouseinHwnd()
        {
            Point mouse_point;
            USER32.GetCursorPos(out mouse_point);
            IntPtr HWND = USER32.WindowFromPoint(mouse_point);
            if (this.HwndDaInviare != USER32.GetDesktopWindow())
            {
                bool trovato = false;
                do
                {
                    if (HWND == IntPtr.Zero)
                        break;
                    else
                        if (HWND == this.HwndDaInviare)
                        {
                            trovato = true;
                            break;
                        }
                    HWND = USER32.GetParent(HWND);
                } while (true);

                if (!trovato) // solo se non mando l'immagine del desktop
                {

                    mouse_point = new Point(-100, -100);
                }
                else
                {
                    USER32.WINDOWPLACEMENT placement = new USER32.WINDOWPLACEMENT();
                    USER32.GetWindowPlacement(HWND, ref placement);

                    mouse_point.X -= placement.rcNormalPosition.X;
                    mouse_point.Y -= placement.rcNormalPosition.Y;
                }
            }
            if (porzione_schermo != null)
            {
                mouse_point.X -= porzione_schermo.X;
                mouse_point.Y -= porzione_schermo.Y;
            }
            return mouse_point;
        }

        #region Bottone di Condivisione
        private delegate void StartStopCondivisioneDelegate(object sender, EventArgs e);
        private void StartStopCondivisione(object sender, EventArgs e)
        {
            if (this.InvokeRequired)
            {
                 IAsyncResult r =  BeginInvoke(
            new StartStopCondivisioneDelegate(StartStopCondivisione), 
            new object[] { sender,e });
                 EndInvoke(r);
            }
            else
            {
                if (this.ChooseHWnd.Enabled || this.choose_rect)
                {
                    MessageBox.Show("Prima devi selezionare cosa inviare");
                }
                else
                {
                    if (TimerRefresh.Enabled)
                    {
                        TimerRefresh.Stop();
                        this.Condividi.Text = "Start";
                        this.Condividi.Image = Properties.Resources.start_icon;
                    }
                    else
                    {
                        this.Condividi.Text = "Stop";
                        this.Condividi.Image = Properties.Resources.stop_icon;
                        TimerRefresh.Start();
                    }
                }
            }
        }
        #endregion
        #endregion

        /// <summary>
        /// Riduce il panel 1
        /// </summary>
        private void Riduci()
        {
            this.splitContainer1.Panel1Collapsed = true;
        }

        private void toolStripButton1_Click_1(object sender, EventArgs e)
        {
            this.Riduci();
        }

        private void toolStripContainer1_ContentPanel_Load(object sender, EventArgs e)
        {

        }

        private void toolStripButton2_Click(object sender, EventArgs e)
        {
            if (splitContainer1.Panel1Collapsed == true)
            {
                this.RiduciButton.Image = Properties.Resources.collapse_left;
                this.Size = this.panel1Size;
                splitContainer1.Panel1Collapsed = false;
            }
            else
            {
                this.RiduciButton.Image = Properties.Resources.collapse_right;
                this.Size = new Size(this.splitContainer1.Panel2.Width, this.Height);
                splitContainer1.Panel1Collapsed = true;
            }
        }

        private void ChooseHWnd_Tick(object sender, EventArgs e)
        {
            Point mousepoint;
            USER32.GetCursorPos(out mousepoint);
            this.HwndDaInviare = USER32.WindowFromPoint(mousepoint);
            lock (lock_BoxImmagine)
            {
                if (this.BoxImmagine.Image != null)
                    this.BoxImmagine.Image.Dispose();
                this.BoxImmagine.Image = CatturaSchermo.GetImageFromHwnd(this.HwndDaInviare);
            }
           
        }

        private void esciToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.Dispose();
        }
        private void CambioVisualizzazione(object sender, EventArgs e)
        {
            if (VisualizzazioneComboBox.SelectedItem.Equals("Zoom"))
                this.BoxImmagine.SizeMode = PictureBoxSizeMode.Zoom;
            if (VisualizzazioneComboBox.SelectedItem.Equals("Normal"))
                this.BoxImmagine.SizeMode = PictureBoxSizeMode.Normal;
            if (VisualizzazioneComboBox.SelectedItem.Equals("Strech"))
                this.BoxImmagine.SizeMode = PictureBoxSizeMode.StretchImage;
            if (VisualizzazioneComboBox.SelectedItem.Equals("Center"))
                this.BoxImmagine.SizeMode = PictureBoxSizeMode.CenterImage;

        }
        #endregion

        private void toolStripButton3_Click(object sender, EventArgs e)
        {
            this.oggetto_clipboard = Clipboard.GetDataObject();
            if (oggetto_clipboard.GetFormats().Contains(DataFormats.FileDrop))
            {

                string[] name = (string[])oggetto_clipboard.GetData(DataFormats.FileDrop);
                Stream fs = File.Open(name[0], FileMode.Open, FileAccess.Read, FileShare.Read);
                long lenght = fs.Length;
                fs.Close();
                if (lenght > 25 * 1024 * 1024)
                {
                    MessageBox.Show("Non puoi Condividere File più grossi di 25 MB");
                    return;
                }
            }
            InvioBroadCast(Messaggio.ClipboardAvailable());
        }
        private void BoxImmagine_LoadCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                // You got the Error image, e.Error tells you why
            }
        }
        private void InvioPremuto(object sender, EventArgs e)
        {
            if (this.ChatBox.Text == "")
            {
                this.ButtonInvia.Enabled = false;
            }
            else
                this.ButtonInvia.Enabled = true;
        }
        private void TastoInvio(object sender, KeyPressEventArgs e)
        {
            if (e.KeyChar == (char)Keys.Enter && this.ButtonInvia.Enabled)
            {
                e.Handled = true;
                ButtonInvia_Click(null, null);
            }
        }
        private void ChooseRect_Tick(object sender, EventArgs e)
        {
            if (inizio_selezione_finestra == true)
            {
                MouseHook.MouseClickedCallback mouseclicked2 = new MouseHook.MouseClickedCallback(SecondPointChoosed);
                MouseHook hook2 = new MouseHook(mouseclicked2);
                inizio_selezione_finestra = false;
            }
            Point Mouse = new Point();
            USER32.GetCursorPos(out Mouse);
            Point p1 = new Point(Math.Min(this.porzione_schermo.X, Mouse.X), Math.Min(this.porzione_schermo.Y, Mouse.Y));
            Point p2 = new Point(Math.Max(this.porzione_schermo.X, Mouse.X), Math.Max(this.porzione_schermo.Y, Mouse.Y));
            Rectangle nuova_porzione = new Rectangle(p1.X,p1.Y, p2.X - p1.X,p2.Y-p1.Y);
            if (this.porzione_schermo != nuova_porzione)
            {
                porzione_schermo = nuova_porzione;
                USER32.InvalidateRect(IntPtr.Zero, IntPtr.Zero, true);
            }
            transparentform.Location = new Point(porzione_schermo.X - 5, porzione_schermo.Y - 5);
            transparentform.Size = new Size(porzione_schermo.Width + 10, porzione_schermo.Height + 10);
        }
        private void porzioneSchermoToolStripMenuItem_Click(object sender, EventArgs e)
        {

            if (TimerRefresh.Enabled)
            {
                this.StartStopCondivisione(null, null);
            }
            if (ChooseHWnd.Enabled)
            {
                MessageBox.Show("Devi completare la selezione della finestra");
            }
            else if (choose_rect)
            {
                MessageBox.Show("Devi completare la selezione della porzione di schermo");
            }
            else
            {
                choose_rect = true;
                MessageBox.Show("Per Selezionare una porzione di schermo, premere con il tasto DESTRO nel punto iniziale del rettangolo e poi di nuovo nel punto finale");
                this.condividiSchermoToolStripMenuItem.Checked = false;
                this.condividiFinestraToolStripMenuItem.Checked = false;
                this.porzioneSchermoToolStripMenuItem.Checked = true;
                    MouseHook.MouseClickedCallback mouseclicked = new MouseHook.MouseClickedCallback(FirstPointChoosed);
                    MouseHook hook = new MouseHook(mouseclicked);
            }
        }
        private void FirstPointChoosed()
        {
            MouseHook.UnHook();
            this.HwndDaInviare = USER32.GetDesktopWindow();
            Point mouse_p = new Point();
            USER32.GetCursorPos(out mouse_p);
            porzione_schermo = new Rectangle(mouse_p,new Size());
            inizio_selezione_finestra = true;
            ChooseRect.Start();
        }
        private void SecondPointChoosed()
        {
            MouseHook.UnHook();
            ChooseRect.Stop();
            USER32.InvalidateRect(IntPtr.Zero, IntPtr.Zero, true);
            choose_rect = false;
        }
        private void RettangoloRossoTefresh_Tick(object sender, EventArgs e)
        {
            RettangoloRosso();
        }
        private void InvalidaSchermo_Tick(object sender, EventArgs e)
        {
            USER32.InvalidateRect(IntPtr.Zero, IntPtr.Zero, true);
        }

    }
}
